
import java.util.ArrayList;
import java.util.List;

/**
 *
 */
public class Individual
{
    private static double randnum;
    
    public int deltaFitness;
    public double rawFitness;
    public double dynamicFitness;
    public double sclFitness;
    public double proFitness;
    private Ship[] ships;

    Individual()
    {
        this.dynamicFitness = -1;
        this.deltaFitness = Integer.MAX_VALUE;
    	this.rawFitness = -1;   //  Fitness not yet evaluated
        this.sclFitness = -1;   //  Fitness not yet scaled
        this.proFitness = -1;   //  Fitness not yet proportionalized
    }
    
    Individual(Ship[] ships)
    {
    	//Assume that ship array passed is a copy
        this.ships = ships;
        
        switch (Parameters.initHeuristics)
        {
            case 1:
                //Do not keep ships within bounds
                for (Ship ship : ships)
                {
                    ship.orientation = Search.r.nextInt(Ship.MAX_ORIENTATIONS);
                    ship.locationX = Search.r.nextInt(Search.solitaireBattleship.board.width);
                    ship.locationY = Search.r.nextInt(Search.solitaireBattleship.board.height);
                }
                break;
            case 2:
                //Keep ships within bounds
                for (Ship ship : ships)
                {
                    do {
                        ship.orientation = Search.r.nextInt(Ship.MAX_ORIENTATIONS);
                        ship.locationX = Search.r.nextInt(Search.solitaireBattleship.board.width);
                        ship.locationY = Search.r.nextInt(Search.solitaireBattleship.board.height);
                    }
                    while (!Search.solitaireBattleship.board.isShipWithinBounds(ship));
                }
                break;
            case 3:
                //For longer ships, we only put them in a column/row for which they fit
                for (Ship ship : ships)
                {
                    if (ship.getLength() > 1)
                    {
                        List<Integer> possibleRows = new ArrayList<Integer>();
                        List<Integer> possibleColumns = new ArrayList<Integer>();

                        //Find possible rows and columns where the ship fits
                        for (int i = 0; i < Search.solitaireBattleship.board.height; i++)
                        {
                            if (Search.solitaireBattleship.board.rowTargetList.get(i) >= ship.getLength())
                            {
                                possibleRows.add(i);
                            }
                        }
                        for (int i = 0; i < Search.solitaireBattleship.board.width; i++)
                        {
                            if (Search.solitaireBattleship.board.columnTargetList.get(i) >= ship.getLength())
                            {
                                possibleColumns.add(i);
                            }
                        }

                        boolean shipPlaced = false;
                        do {
                            //Use random possible column
                            if (Search.r.nextBoolean())
                            {
                                if (possibleColumns.size() > 0)
                                {
                                    ship.locationX = possibleColumns.get(Search.r.nextInt(possibleColumns.size()));
                                    ship.locationY = Search.r.nextInt(Search.solitaireBattleship.board.height);

                                    //Use random up/down orientation
                                    if (Search.r.nextBoolean())
                                        ship.orientation = Ship.ORIENTATION_DOWN;
                                    else
                                        ship.orientation = Ship.ORIENTATION_UP;

                                    shipPlaced = true;
                                }
                            }
                            //Use random possible row
                            else
                            {
                                if (possibleRows.size() > 0)
                                {
                                    ship.locationX = Search.r.nextInt(Search.solitaireBattleship.board.width);
                                    ship.locationY = possibleRows.get(Search.r.nextInt(possibleRows.size()));

                                    //Use random left/right orientation
                                    if (Search.r.nextBoolean())
                                        ship.orientation = Ship.ORIENTATION_LEFT;
                                    else
                                        ship.orientation = Ship.ORIENTATION_RIGHT;

                                    shipPlaced = true;
                                }
                            }
                        } while (!shipPlaced);
                    }
                    else
                    {
                        ship.orientation = Search.r.nextInt(Ship.MAX_ORIENTATIONS);
                        ship.locationX = Search.r.nextInt(Search.solitaireBattleship.board.width);
                        ship.locationY = Search.r.nextInt(Search.solitaireBattleship.board.height);
                    }
                }
                break;
        }
        
        this.dynamicFitness = -1;
        this.deltaFitness = Integer.MAX_VALUE;
        this.rawFitness = -1;   //  Fitness not yet evaluated
        this.sclFitness = -1;   //  Fitness not yet scaled
        this.proFitness = -1;   //  Fitness not yet proportionalized
    }
    
    public boolean equivalent(Individual i2)
    {
        for (int i = 0; i < ships.length; i++)
        {
            if (!ships[i].equivalent(i2.ships[i]))
                return false;
        }
        
        return true;
    }

    public Individual clone()
    {
    	Individual newIndividual = new Individual();
    	
    	newIndividual.setShips(SolitaireBattleship.getDeepCopyOfShips(this.ships));
    	newIndividual.proFitness = this.proFitness;
    	newIndividual.rawFitness = this.rawFitness;
    	newIndividual.dynamicFitness = this.dynamicFitness;
    	newIndividual.deltaFitness = this.deltaFitness;
    	newIndividual.sclFitness = this.sclFitness;
        
    	return newIndividual;
    }
    
    public int length()
    {
        return ships.length;
    }

    public Ship getShip(int index)
    {
        return ships[index];
    }

    public Ship[] getShips()
    {
        return ships;
    }

    public Ship[] getCopyOfShips()
    {
        return ships.clone();
    }

    public void setShips(Ship[] newShips)
    {
        this.ships = newShips;
    }

    public static int selectParent()
    {

        double rWheel = 0;
        int j = 0;
        int k = 0;

        switch (Parameters.selectType) {

            case 1:     // Proportional Selection
                randnum = Search.r.nextDouble();
                for (j = 0; j < Parameters.popSize; j++) {
                    rWheel = rWheel + Search.member[j].proFitness;
                    if (randnum < rWheel) {
                        return (j);
                    }
                }
                break;

            case 3:     // Random Selection
                randnum = Search.r.nextDouble();
                j = (int) (randnum * Parameters.popSize);
                return (j);

            case 2:     //  Tournament Selection
                randnum = Search.r.nextDouble();
                j = (int) (randnum * Parameters.popSize);
                randnum = Search.r.nextDouble();
                k = (int) (randnum * Parameters.popSize);
                randnum = Search.r.nextDouble();
                if (Search.member[k].dynamicFitness > Search.member[j].dynamicFitness) {
                    int jt = j;
                    j = k;
                    k = jt;
                }
                double tournamentParam = 0.8;
                if (randnum < tournamentParam) {
                    return (j);
                } else {
                    return (k);
                }
            default:
                System.out.println("ERROR - No selection method selected");
        }
        return (-1);
    }

    public void doMutation()
    {
        int rangeX = Search.solitaireBattleship.board.width/2;
        int rangeY = Search.solitaireBattleship.board.height/2;
        
        //Orientation
		for (int i = 0; i < this.ships.length; i++)
        {
			if(Search.r.nextDouble() < Parameters.mutationRate)
            {
				this.ships[i].orientation = Search.r.nextInt(Ship.MAX_ORIENTATIONS);
            }
        }

        switch (Parameters.mutationType)
        {
            case 1:
                //Location - Guassian, mutate X or Y according to orientation, do not keep ship within bounds
                for (int i = 0; i < this.ships.length; i++)
                {
                    if(Search.r.nextDouble() < Parameters.mutationRate)
                    {
                        //move ship in direction of orientation
                        if (ships[i].orientation == Ship.ORIENTATION_DOWN || ships[i].orientation == Ship.ORIENTATION_UP)
                        {
                            ships[i].locationY += (int)(rangeY * Search.r.nextGaussian());
                        }
                        else
                        {
                            ships[i].locationX += (int)(rangeX * Search.r.nextGaussian());
                        }
                    }
                }
                break;
        
            case 2:
                //Location - Guassian, mutate X and Y independently, do not keep ship within bounds
                for (int i = 0; i < this.ships.length; i++)
                {
                    if (Search.r.nextDouble() < Parameters.mutationRate)
                    {
                        ships[i].locationX += (int)(rangeX * Search.r.nextGaussian());
                    }
                    if (Search.r.nextDouble() < Parameters.mutationRate)
                    {
                        ships[i].locationY += (int)(rangeY * Search.r.nextGaussian());
                    }
                }
                break;
            
            case 3:
                //Location - Guassian, both X and Y, keep ship within bounds
                for (int i = 0; i < this.ships.length; i++)
                {
                    if(Search.r.nextDouble() < Parameters.mutationRate)
                    {
                        int originalX = ships[i].locationX;
                        int originalY = ships[i].locationY;
                        do {
                            ships[i].locationX = originalX;
                            ships[i].locationY = originalY;

                            ships[i].locationX += (int)(rangeX * Search.r.nextGaussian());
                            ships[i].locationY += (int)(rangeY * Search.r.nextGaussian());
                        }
                        while (!Search.solitaireBattleship.board.isShipWithinBounds(ships[i]));
                    }
                }
                break;
        }
    }

    public static void mateParents(int pnum1, int pnum2, Individual parent1, Individual parent2, Individual child1, Individual child2, int level)
    {

        int xoverPoint1;
        int xoverPoint2;

        int chromoSize = parent1.ships.length;

        switch (Parameters.xoverType) {

            case 1: //Crossover as described in the paper
                
                Individual bestChild1 = new Individual();
                Individual bestChild2 = new Individual();
                
                //Try all possible pivot points
                for (int pivot = 1; pivot < chromoSize-1; pivot++)
                {
                    //Make children for given pivot
                    for (int i = pivot; i < chromoSize; i++)
                    {
                        if (i > pivot)
                        {
                            child1.ships[i] = new Ship(parent2.ships[i]);
                            child2.ships[i] = new Ship(parent1.ships[i]);
                        }
                        else
                        {
                            child1.ships[i] = new Ship(parent1.ships[i]);
                            child2.ships[i] = new Ship(parent2.ships[i]);
                        }
                    }
                    
                    //Get fitness of new child
                    Search.solitaireBattleship.doRawFitness(child1, level);
                    Search.solitaireBattleship.doRawFitness(child2, level);
                    
                    //One child has a better fitness than the current best
                    if (Math.max(child1.dynamicFitness, child2.dynamicFitness) > Math.max(bestChild1.dynamicFitness, bestChild2.dynamicFitness))
                    {
                        //Set new current best
                        bestChild1 = child1.clone();
                        bestChild2 = child2.clone();
                    }
                }
                
                //Copy best to children
                child1 = bestChild1;
                child2 = bestChild2;
                break;

            case 2: //1-point crossover

                //Select crossover point
                xoverPoint1 = Search.r.nextInt(chromoSize-2) + 1;
                
                for (int i = xoverPoint1; i < chromoSize; i++)
                {
                    if (i > xoverPoint1)
                    {
                        child1.ships[i] = new Ship(parent2.ships[i]);
                        child2.ships[i] = new Ship(parent1.ships[i]);
                    }
                    else
                    {
                        child1.ships[i] = new Ship(parent1.ships[i]);
                        child2.ships[i] = new Ship(parent2.ships[i]);
                    }
                }
                
                break;
			case 3: //Exhaustive Uniform Crossover

                //Select crossover point
                Individual bestChild = new Individual();
                Individual bestComplement = new Individual();
                
                //int mask = new int[parent1.length()];
                //Arrays.fill(mask, 0);                
                
                for (int i = 0; i < Math.pow(2,parent1.length()-1); i++)
                {
                  for (int j = 0; j < parent1.length();j++)  
                  {

                    if (((i >> j) & 1) == 1)
                    {
                        child1.ships[j] = new Ship(parent2.ships[j]);
                        child2.ships[j] = new Ship(parent1.ships[j]);
                    }
                    else
                    {
                        child1.ships[j] = new Ship(parent1.ships[j]);
                        child2.ships[j] = new Ship(parent2.ships[j]);
                    }
                  }
                  if (Math.max(child1.rawFitness, child2.rawFitness) > Math.max(bestChild.rawFitness, bestComplement.rawFitness))
                  {
                    bestChild = child1.clone();
                    bestComplement = child2.clone();
                  }
                }
                child1 = bestChild;
                child2 = bestComplement;
                break;
            default:
                System.out.println("ERROR - Bad crossover method selected");
                break;
        }

        //  Set fitness values back to zero
        child1.dynamicFitness = -1;
        child1.deltaFitness = Integer.MAX_VALUE;
        child1.rawFitness = -1;   //  Fitness not yet evaluated
        child1.sclFitness = -1;   //  Fitness not yet scaled
        child1.proFitness = -1;   //  Fitness not yet proportionalized
        child2.dynamicFitness = -1;
        child2.deltaFitness = Integer.MAX_VALUE;
        child2.rawFitness = -1;   //  Fitness not yet evaluated
        child2.sclFitness = -1;   //  Fitness not yet scaled
        child2.proFitness = -1;   //  Fitness not yet proportionalized
    }

    public static void mateParents(int pnum, Individual parent, Individual child)
    {

        child.setShips(parent.getCopyOfShips());

        //  Set fitness values back to zero
        child.dynamicFitness = -1;
        child.deltaFitness = Integer.MAX_VALUE;
        child.rawFitness = -1;   //  Fitness not yet evaluated
        child.sclFitness = -1;   //  Fitness not yet scaled
        child.proFitness = -1;   //  Fitness not yet proportionalized
    }

    public void Print()
    {
        for (Ship s: ships)
        {
            System.out.println(s.locationX + " " + s.locationY + " " + s.orientation);
        }
    }
}
